﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.Web.UI;
	using System.Web.UI.WebControls;
	using System.ComponentModel;
	using System.Globalization;
	using System.Diagnostics.CodeAnalysis;
	
	/// <summary>
	/// Server control used to generate a jQuery UI Accordion widget.
	/// </summary>
	[ParseChildren(false), PersistChildren(true)]
	public class Accordion : JQueryWebControl {


		#region Constants: Property defaults

		private const int defaultActive = 0;
		private const bool defaultAutoHeight = true;
		private const bool defaultClearStyle = false;
		private const bool defaultCollapsible = false;
		private const PanelActivateEvent defaultEvent = PanelActivateEvent.Click;
		private const bool defaultFillSpace = false;
		private const HeaderLevel defaultHeader = HeaderLevel.H3;
		private const Symbol defaultHeaderIconSymbol = Symbol.Triangle1E;
		private const string defaultHeaderIconCssClass = "";
		private const Symbol defaultSelectedHeaderIconSymbol = Symbol.Triangle1S;
		private const string defaultSelectedHeaderIconCssClass = "";

		#endregion


		#region Static Methods: Private routines

		private static string GetIconCssClass(string iconCssClass, Symbol iconSymbol, Symbol defaultIconSymbol) {
			if(!string.IsNullOrEmpty(iconCssClass)) return iconCssClass;
			if(iconSymbol == defaultIconSymbol) return null;
			return iconSymbol.GetCssClass();
		}

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.JQuery.Accordion class.
		/// </summary>
		[DefaultValue(defaultActive), Category("Appearance")]
		public Accordion() : base(HtmlTextWriterTag.Div) { }

		#endregion


		#region Properties: Widget initialization options

		//TODO: This is simply too fragile.  Lets call it ActivePanel and change it to use a ViewState control name reference, and set field with clientId, init with visible index and postback with clientId
		/// <summary>
		/// Zero-based index of the panel to be visible on initialization.
		/// This property maps directly to the "active" option when the jQuery UI widget is initialized.
		/// </summary>
		public virtual int? Active {
			get {
				EnsureChildControls();
				string value = ActiveField.Value;
				return string.IsNullOrEmpty(value) ? (int?)null : int.Parse(value, CultureInfo.InvariantCulture);
			}
			set {
				EnsureChildControls();
				ActiveField.Value = value.HasValue ? value.Value.ToString(CultureInfo.InvariantCulture) : string.Empty;
			}
		}

		/// <summary>
		/// If set, the highest content part is used as height reference for all other parts. Provides more consistent animations.
		/// This property maps directly to the "autoHeight" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="ClearStyle"/>
		[DefaultValue(defaultAutoHeight), Category("Behavior")]
		public virtual bool AutoHeight {
			get { return ViewState.GetValue("AutoHeight", defaultAutoHeight); }
			set { ViewState["AutoHeight"] = value; }
		}

		/// <summary>
		/// If set, clears height and overflow styles after finishing animations. This enables accordions to work with dynamic content. Won't work together with AutoHeight.
		/// This property maps directly to the "clearStyle" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <see cref="AutoHeight"/>
		[DefaultValue(defaultClearStyle), Category("Behavior")]
		public virtual bool ClearStyle {
			get { return ViewState.GetValue("ClearStyle", defaultClearStyle); }
			set { ViewState["ClearStyle"] = value; }
		}

		/// <summary>
		/// Whether all the sections can be closed at once. Allows collapsing the active section by the triggering event.
		/// This property maps directly to the "collapsible" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultCollapsible), Category("Behavior")]
		public virtual bool Collapsible {
			get { return ViewState.GetValue("Collapsible", defaultCollapsible); }
			set { ViewState["Collapsible"] = value; }
		}

		/// <summary>
		/// The event on which to trigger the accordion.
		/// This property maps directly to the "event" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <exception cref="ArgumentException">Value not defined.</exception>
		[DefaultValue(defaultEvent), Category("Behavior")]
		[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Event", Justification = "Exposed as the jQuery option name.")]
		public virtual PanelActivateEvent Event {
			get { return ViewState.GetValue("Event", defaultEvent); }
			set {
				if(!Enum.IsDefined(typeof(PanelActivateEvent), value)) throw new ArgumentException("Value not defined.", "value");
				ViewState["Event"] = value;
			}
		}

		/// <summary>
		/// If set, the accordion completely fills the height of the parent element. Overrides AutoHeight.
		/// This property maps directly to the "fillSpace" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultFillSpace), Category("Behavior")]
		public virtual bool FillSpace {
			get { return ViewState.GetValue("FillSpace", defaultFillSpace); }
			set { ViewState["FillSpace"] = value; }
		}

		/// <summary>
		/// Selector for the header element.
		/// Controls the HTML tag that will be used to render the HeaderText property on the contained AccordionPanel controls.
		/// This property maps directly to the "header" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <exception cref="ArgumentException">Value not defined.</exception>
		[DefaultValue(defaultHeader), Category("Layout")]
		public virtual HeaderLevel Header {
			get { return ViewState.GetValue("Header", defaultHeader); }
			set {
				if(!Enum.IsDefined(typeof(HeaderLevel), value)) throw new ArgumentException("Value not defined.", "value");
				ViewState["Header"] = value;
			}
		}

		/// <summary>
		/// Standard native themeable jQuery UI CSS Framework icon to use for non-selected headers.  To assign a custom icon, use the HeaderIconCssClass property.
		/// Value maps to the "header" sub-option nested within the "icons" option when the jQuery widget is initialized.
		/// </summary>
		/// <seealso cref="HeaderIconCssClass"/>
		/// <seealso cref="SelectedHeaderIconSymbol"/>
		/// <exception cref="ArgumentException">Value not defined.</exception>
		[DefaultValue(defaultHeaderIconSymbol), Category("Appearance")]
		public virtual Symbol HeaderIconSymbol {
			get { return ViewState.GetValue("HeaderIconSymbol", defaultHeaderIconSymbol); }
			set {
				if(!Enum.IsDefined(typeof(Symbol), value)) throw new ArgumentException("Value not defined.", "value");
				ViewState["HeaderIconSymbol"] = value;
			}
		}

		/// <summary>
		/// Custom icon to use for non-selected headers.  Using a standard native themeable jQuery UI CSS Framework icon is preferred using the HeaderIconSymbol property.
		/// Value maps to the "header" sub-option nested within the "icons" option when the jQuery widget is initialized.
		/// </summary>
		/// <seealso cref="HeaderIconSymbol"/>
		/// <seealso cref="SelectedHeaderIconCssClass"/>
		[DefaultValue(defaultHeaderIconCssClass), Category("Appearance"), CssClassProperty]
		public virtual string HeaderIconCssClass {
			get { return ViewState.GetValue("HeaderIconCssClass", defaultHeaderIconCssClass); }
			set { ViewState["HeaderIconCssClass"] = value; }
		}

		/// <summary>
		/// Standard native themeable jQuery UI CSS Framework icon to use for the selected header.  To assign a custom icon, use the SelectedHeaderIconCssClass property.
		/// Value maps to the "selectedHeader" sub-option nested within the "icons" option when the jQuery widget is initialized.
		/// </summary>
		/// <seealso cref="HeaderIconSymbol"/>
		/// <seealso cref="SelectedHeaderIconCssClass"/>
		/// <exception cref="ArgumentException">Value not defined.</exception>
		[DefaultValue(defaultSelectedHeaderIconSymbol), Category("Appearance")]
		public virtual Symbol SelectedHeaderIconSymbol {
			get { return ViewState.GetValue("SelectedHeaderIconSymbol", defaultSelectedHeaderIconSymbol); }
			set {
				if(!Enum.IsDefined(typeof(Symbol), value)) throw new ArgumentException("Value not defined.", "value");
				ViewState["SelectedHeaderIconSymbol"] = value;
			}
		}

		/// <summary>
		/// Custom icon to use for the selected header.  Using a standard native themeable jQuery UI CSS Framework icon is preferred using the SelectedHeaderIconSymbol property.
		/// Value maps to the "selectedHeader" sub-option nested within the "icons" option when the jQuery widget is initialized.
		/// </summary>
		/// <seealso cref="HeaderIconSymbol"/>
		/// <seealso cref="SelectedHeaderIconCssClass"/>
		[DefaultValue(defaultSelectedHeaderIconCssClass), Category("Appearance"), CssClassProperty]
		public virtual string SelectedHeaderIconCssClass {
			get { return ViewState.GetValue("SelectedHeaderIconCssClass", defaultSelectedHeaderIconCssClass); }
			set { ViewState["SelectedHeaderIconCssClass"] = value; }
		}

		#endregion


		#region Properties: Subcontrol

		/// <summary>
		/// Gets or sets the hidden field used to round-trip the Active property.
		/// </summary>
		protected virtual PostBackField ActiveField { get; set; }

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the Active property.
		/// </summary>
		public virtual string ActiveClientId {
			get { return ActiveField.ClientID; }
		}

		#endregion


		#region Events

		/// <summary>
		/// Occurs on postback when the Active property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler ActiveChanged;

		#endregion


		#region Methods: Event hooks

		/// <summary>
		/// Raises the ActiveChanged event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected virtual void OnActiveChanged(EventArgs e) {
			if(ActiveChanged != null) ActiveChanged.Invoke(this, e);
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Allows a jQuery server control to reference scripts and stylesheets necessary to render their client markup and widgets.
		/// </summary>
		/// <param name="manager">The GreenIsomer.Web.Controls.JQuery.Manager used to handle style and script referencing in addition to unified generation of load and ready scripts.</param>
		protected override void Register(Manager manager) {

			manager.RegisterScriptFile(Settings.Default.CoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiCoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiAccordionScriptFile);

			manager.RegisterStyleFile(Settings.Default.UiCoreStyleFile);
			manager.RegisterStyleFile(Settings.Default.UiAccordionStyleFile);
			manager.RegisterThemeStyleFile();

			manager.RegisterControl(this);

			EnsureChildControls();
		}

		/// <summary>
		/// Sends server control content to a provided System.Web.UI.HtmlTextWriter object, which writes the content to be rendered to the client.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="writer">A System.Web.UI.HtmlTextWriter that represents the output stream to render HTML content on the client.</param>
		protected override void Render(HtmlTextWriter writer) {

			//TODO: Let's revisit the whole "degradability versus getting rid of flicker issue"  I don't think this falls under UseCssStyling, perhaps a DegradeForNonJs... Or is that a moot point here?
			if(!MasterUseCssStyling)
				writer.AddStyleAttribute("display", "none");

			base.Render(writer);
		}

		/// <summary>
		/// Renders the contents of the control to the specified writer.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="writer">A System.Web.UI.HtmlTextWriter that represents the output stream to render HTML content on the client.</param>
		protected override void RenderContents(HtmlTextWriter writer) {
			writer.Indent++;
			base.RenderContents(writer);
		}

		/// <summary>
		/// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparatin for posting back or rendering.
		/// This method is used primarily by control developers.
		/// </summary>
		protected override void CreateChildControls() {
			base.CreateChildControls();

			ActiveField = new PostBackField();
			//TODO: This has to be resolved!
			ActiveField.ID = ID + ClientIDSeparator + "Active";
			ActiveField.Value = defaultActive.ToString(CultureInfo.InvariantCulture);
			ActiveField.ValueChanged += new EventHandler<PostBackFieldEventArgs>(ActiveField_ValueChanged);
			Controls.Add(ActiveField);
		}

		/// <summary>
		/// Renders the load script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderLoadScript(ScriptBuilder script) {
			string clientId = ClientID;

			//TODO: Let's revisit the whole "degradability versus getting rid of flicker issue"  I don't think this falls under UseCssStyling, perhaps a DegradeForNonJs... Or is that a moot point here?
			if(!MasterUseCssStyling) {
				script.RenderSelector(clientId);
				script.Append(".css('display', ''); ");
			}

			script.RenderSelector(clientId);
			script.Append(".accordion(");
			script.RenderProperties(CreateProperties());
			script.Append(");");
		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <returns>A GreenIsomer.Web.Controls.JQuery.Properties object that will become the jQuery UI initialization options.</returns>
		protected override PropertyCollection CreateProperties() {
			PropertyCollection properties = base.CreateProperties();

			int? active = Active;
			if(active != defaultActive) {
				if(active.HasValue) properties.AddValue("active", active.Value);
				else properties.AddValue("active", false);
			}

			bool autoHeight = AutoHeight;
			if(autoHeight != defaultAutoHeight) properties.AddValue("autoHeight", autoHeight);

			ScriptBuilder changeScript = new ScriptBuilder();
			RenderChangeScript(changeScript);
			if(changeScript.Length != 0) properties.AddScript("change", changeScript);

			bool clearStyle = ClearStyle;
			if(clearStyle != defaultClearStyle) properties.AddValue("clearStyle", clearStyle);

			bool collapsible = Collapsible;
			if(collapsible != defaultCollapsible) properties.AddValue("collapsible", collapsible);

			PanelActivateEvent @event = Event;
			if(@event != defaultEvent) properties.AddEnumValue("event", @event);

			bool fillSpace = FillSpace;
			if(fillSpace != defaultFillSpace) properties.AddValue("fillSpace", fillSpace);

			HeaderLevel header = Header;
			properties.AddEnumValue("header", header);

			string headerIconCssClass = GetIconCssClass(HeaderIconCssClass, HeaderIconSymbol, defaultHeaderIconSymbol);
			string selectedHeaderIconCssClass = GetIconCssClass(SelectedHeaderIconCssClass, SelectedHeaderIconSymbol, defaultSelectedHeaderIconSymbol);
			if(!string.IsNullOrEmpty(headerIconCssClass) || !string.IsNullOrEmpty(selectedHeaderIconCssClass)) {
				PropertyCollection iconProperties = new PropertyCollection();
				if(!string.IsNullOrEmpty(headerIconCssClass)) iconProperties.AddValue("header", headerIconCssClass);
				if(!string.IsNullOrEmpty(selectedHeaderIconCssClass)) iconProperties.AddValue("selectedHeader", selectedHeaderIconCssClass);
				properties.AddValue("icons", iconProperties);
			}

			return properties;
		}

		/// <summary>
		/// Renders the client "change" event script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderChangeScript(ScriptBuilder script) {

			script.Append("function(event,ui){ ");

			script.RenderSelector(ActiveClientId);
			script.Append(".val($(this).find(");
			script.AppendEnumValue(Header);
			script.Append(").index(ui.newHeader[0]));");

			script.Append("}");

		}

		#endregion


		#region Event handlers

		private void ActiveField_ValueChanged(object sender, PostBackFieldEventArgs e) {
			OnActiveChanged(EventArgs.Empty);
		}

		#endregion


	}

}